package pkg.andru.watcher;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import pkg.andru.util.Log;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;

/**
 * system event manager will watch some system event, pre-define some events
 * like {@link Event#SYS_EVENT_MEDIA_EJECT},
 * {@link Event#SYS_EVENT_MEDIA_MOUNTED}, etc...<br>
 * you can also add your defined system event use
 * {@link #addEvent(int, IntentFilter)} </p> if you want to listener the event
 * occur, you can use {@link #registerEventListener(OnEventListener)} to
 * listener the event, sametime when you dont care, you should invoke
 * {@link #unRegisterEventListener(OnEventListener)}
 * 
 * <P>
 * <b>some event need the uses-permission, like
 * {@link Event#SYS_EVENT_NETWORK_CONNECT_CHANGED} need the
 * android.permission.ACCESS_NETWORK_STATE permission, more detail please check
 * {@link Event}</b>
 * 
 * @author idiottiger
 * @version 1.0
 * 
 */
public final class EventManager {

    static final String LOG_TAG = "EventManager";

    static EventManager mEventManager;

    final List<WeakReference<OnEventListener>> mListenerList = new LinkedList<WeakReference<OnEventListener>>();
    final Map<String, Integer> mEventActionMap = new HashMap<String, Integer>();

    EventInnerWatcher mWatcher;

    private Context mApp;

    private EventManager() {
        initEventManager();
    }

    /**
     * get event manager instance, use singletone
     * 
     * @return
     */
    public static EventManager getInstance() {
        if (mEventManager == null) {
            mEventManager = new EventManager();
        }
        return mEventManager;
    }

    private void initEventManager() {
        mListenerList.clear();
        mEventActionMap.clear();
    }

    /**
     * start event watch
     * 
     * @param context
     */
    public void startEventWatch(Context context) {
        if (mWatcher == null) {
            mApp = context;
            mWatcher = new EventInnerWatcher();
            mWatcher.startWatch();
        }
    }

    /**
     * stop the event watch, if invoke this, <b>your events added using
     * {@link #addEvent(int, IntentFilter)} will be removed</b>
     */
    public void stopEventWatch() {
        if (mWatcher != null) {
            mWatcher.stopWatch();
            mWatcher = null;
        }
        unRegisterAllEventListeners();
        mApp = null;
    }

    /**
     * register to the event dispatcher
     * 
     * @param listener
     */
    public void registerEventListener(OnEventListener listener) {
        synchronized (mListenerList) {
            if (listener != null) {
                mListenerList.add(new WeakReference<OnEventListener>(listener));
            }
        }
    }

    /**
     * unregister to the event dispatcher
     * 
     * @param listener
     */
    public void unRegisterEventListener(OnEventListener listener) {
        synchronized (mListenerList) {
            if (listener != null) {
                Iterator<WeakReference<OnEventListener>> it = mListenerList.iterator();
                while (it.hasNext()) {
                    final WeakReference<OnEventListener> item = it.next();
                    final OnEventListener sl = item.get();
                    if (sl == null || sl == listener) {
                        it.remove();
                    }
                }
            }
        }
    }

    /**
     * unregister all event listeners
     */
    public void unRegisterAllEventListeners() {
        synchronized (mListenerList) {
            mListenerList.clear();
        }
    }

    /**
     * add system event what you want to add, suggestion the filter have one
     * action better
     * 
     * @param event
     *            event type, need diff the system exsited system event
     * @param filter
     *            the intent filter
     */
    public void addEvent(int event, IntentFilter filter) {
        mWatcher.addWatch(event, filter);
    }

    /**
     * 
     * @author idiottiger
     * 
     */
    class EventInnerWatcher extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            final int event = mEventActionMap.get(action);
            Log.d(LOG_TAG, "event:[action:" + action + ",type:" + event+"]");
            synchronized (mListenerList) {
                if (!mListenerList.isEmpty()) {
                    for (WeakReference<OnEventListener> wlistener : mListenerList) {
                        final OnEventListener listener = wlistener.get();
                        if (listener != null) {
                            listener.handleEvent(event, intent);
                        }
                    }
                }
            }
        }

        public void startWatch() {
            IntentFilter mediaStatusFilter = new IntentFilter();
            mediaStatusFilter.addAction(Intent.ACTION_MEDIA_EJECT);
            mediaStatusFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
            mediaStatusFilter.addDataScheme("file");
            mApp.registerReceiver(this, mediaStatusFilter);
            mEventActionMap.put(Intent.ACTION_MEDIA_EJECT, Event.SYS_EVENT_MEDIA_EJECT);
            mEventActionMap.put(Intent.ACTION_MEDIA_MOUNTED, Event.SYS_EVENT_MEDIA_MOUNTED);

            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
            filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

            mApp.registerReceiver(this, filter);
            mEventActionMap.put(Intent.ACTION_DEVICE_STORAGE_LOW, Event.SYS_EVENT_DEVICE_STORAGE_LOW);
            mEventActionMap.put(Intent.ACTION_DEVICE_STORAGE_OK, Event.SYS_EVENT_DEVICE_STORAGE_OK);
            mEventActionMap.put(ConnectivityManager.CONNECTIVITY_ACTION, Event.SYS_EVENT_NETWORK_CONNECT_CHANGED);
        }

        public void addWatch(int event, IntentFilter filter) {
            if (filter != null) {
                mApp.registerReceiver(this, filter);
                mEventActionMap.put(filter.getAction(0), event);
            }
        }

        public void stopWatch() {
            mApp.unregisterReceiver(this);
            mEventActionMap.clear();
        }
    }

}
